1. sync包的Cond,提供條件變數。
a. 條件變數是基於互斥鎖的,它必須有互斥鎖的支撐,才能使用。
b. 條件變數並不是被用來保護共享資源,它是用來協調想要訪問共享資源的那些goroutines。
當共享資源的狀態發生改變時,它可以被用來通知被互斥鎖阻塞的goroutine。
c. 函數:
func NewCond(l Locker) *Cond
func (c *Cond) Broadcast()
func (c *Cond) Signal()
func (c *Cond) Wait()
func (rw *RWMutex) Lock()
func (rw *RWMutex) Unlock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RUnlock()
2. 條件變數如何和讀/寫互斥鎖一起使用?
參考範例:
package main
import (
"log"
"sync"
"time"
)
func main() {
// mailbox 代表信箱。
// 0代表信箱是空的,1代表信箱是滿的。
var mailbox uint8
// lock 代表信箱上的鎖。
var lock sync.RWMutex
// sendCond 代表專用於發信的條件變數。
sendCond := sync.NewCond(&lock)
// recvCond 代表專用於收信的條件變數。
recvCond := sync.NewCond(lock.RLocker())
// sign 用於傳遞演示完成的信號。
sign := make(chan struct{}, 3)
max := 5
go func(max int) { // 用於發信。
defer func() {
sign <- struct{}{}
}()
for i := 1; i <= max; i++ {
time.Sleep(time.Millisecond * 500)
lock.Lock()
for mailbox == 1 {
sendCond.Wait()
}
log.Printf("sender [%d]: the mailbox is empty.", i)
mailbox = 1
log.Printf("sender [%d]: the letter has been sent.", i)
lock.Unlock()
recvCond.Signal()
}
}(max)
go func(max int) { // 用於收信。
defer func() {
sign <- struct{}{}
}()
for j := 1; j <= max; j++ {
time.Sleep(time.Millisecond * 500)
lock.RLock()
for mailbox == 0 {
recvCond.Wait()
}
log.Printf("receiver [%d]: the mailbox is full.", j)
mailbox = 0
log.Printf("receiver [%d]: the letter has been received.", j)
lock.RUnlock()
sendCond.Signal()
}
}(max)
<-sign
<-sign
}
https://play.golang.org/p/qY4mLfzKSuG
3. 範例細節說明
a. 與sync.Mutex和sync.RWMutex類型不同,sync.Cond類型並不是開箱即用。
需要利用sync.NewCond函數創建它的指標值,而這個函數需要sync.Locker類型的參數值。
b. sendCond和recvCond都是屬於*sync.Cond類型,同時使用sync.NewCond函數初始化。
sendCond := sync.NewCond(&lock)
recvCond := sync.NewCond(lock.RLocker())
sendCond變數在初始化時,要把lock的指標值傳給sync.NewCond函數。
因為,sendCond專門用來對共享資源的寫操作。lock變數的Lock和Unlock方法分別用於對寫鎖的鎖定和解鎖。
recvCond變數在初始化時,只要把RLocker的值傳給sync.NewCond函數。
因為,recvCond只會對共享資源進行讀操作。
c. 使用條件變數(sendCond、recvCond),實現單向通知(sendCond.Signal()、recvCond.Signal())。
sender的goroutine開始執行,會阻塞在sendCond.Wait()等待有人發送Signal(sendCond.Signal())。
當sender收到Signal後,就會繼續往下執行。
而receiver也是。
參考來源:
郝林-Go语言核心36讲
https://github.com/hyper0x/Golang_Puzzlers
https://golang.org/pkg/cmd/go/internal/test/